﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Xml.Schema;
using System.ServiceModel;
using Microsoft.ResourceManagement.Client.WsTransfer;
using Microsoft.ResourceManagement.Client.WsEnumeration;
using Microsoft.ResourceManagement.ObjectModel;
using Microsoft.ResourceManagement.ObjectModel.ResourceTypes;

namespace Microsoft.ResourceManagement.Client
{
    /// <summary>
    /// The DefaultClient is a thin wrapper over the WsTransfer, WsTransferFactory, and WsEnumeration clients.
    /// 
    /// It is intended to be useful only for common scenarios. For more advanced scenarios, please use the other clients.
    /// </summary>
    public class DefaultClient : IDisposable
    {
        // underlying clients
        WsTransferClient wsTransferClient;
        WsTransferFactoryClient wsTransferFactoryClient;
        MexClient mexClient;
        WsEnumerationClient wsEnumerationClient;

        // factories used to construct resources and requests
        RmResourceFactory resourceFactory;
        RmRequestFactory requestFactory;

        bool schemaCached;

        public DefaultClient()
        {
            this.wsTransferClient = new WsTransferClient();
            this.wsTransferFactoryClient = new WsTransferFactoryClient();
            this.wsEnumerationClient = new WsEnumerationClient();
            this.mexClient = new MexClient();

            this.resourceFactory = new RmResourceFactory();
            this.requestFactory = new RmRequestFactory();
        }

        public DefaultClient(
            String wsTransferConfigurationName,
            String wsTransferFactoryConfigurationName,
            String wsEnumerationConfigurationName,
            String mexConfigurationName
            )
        {
            this.wsTransferClient = new WsTransferClient(wsTransferConfigurationName);
            this.wsTransferFactoryClient = new WsTransferFactoryClient(wsTransferFactoryConfigurationName);
            this.wsEnumerationClient = new WsEnumerationClient(wsEnumerationConfigurationName);
            this.mexClient = new MexClient(mexConfigurationName);

            this.resourceFactory = new RmResourceFactory();
            this.requestFactory = new RmRequestFactory();
        }

        public DefaultClient(
            String wsTransferConfigurationName,
            String wsTransferEndpointAddress,
            String wsTransferFactoryConfigurationName,
            String wsTransferFactoryEndpointAddress,
            String wsEnumerationConfigurationName,
            String wsEnumerationEndpointAddress,
            String mexConfigurationName,
            String mexEndpointAddress
            )
        {
            this.wsTransferClient = new WsTransferClient(wsTransferConfigurationName, wsTransferEndpointAddress);
            this.wsTransferFactoryClient = new WsTransferFactoryClient(wsTransferFactoryConfigurationName, wsTransferFactoryEndpointAddress);
            this.wsEnumerationClient = new WsEnumerationClient(wsEnumerationConfigurationName, wsEnumerationEndpointAddress);
            this.mexClient = new MexClient(mexConfigurationName, mexEndpointAddress);

            this.resourceFactory = new RmResourceFactory();
            this.requestFactory = new RmRequestFactory();
        }

        /// <summary>
        /// Refreshes the metadata from the service.
        /// </summary>
        public XmlSchemaSet RefreshSchema()
        {
            XmlSchemaSet metadata = this.mexClient.Get();
            lock (this.requestFactory)
            {
                this.requestFactory = new RmRequestFactory(metadata);
            }
            lock (this.resourceFactory)
            {
                this.resourceFactory = new RmResourceFactory(metadata);
            }
            this.schemaCached = true;
            return metadata;
        }
        #region WsTransfer
        /// <summary>
        /// Creates the given resource and returns its ObjectId.
        /// This method does not set the ObjectId of newResource.
        /// </summary>
        /// <param name="newResource">The resource to create.</param>
        /// <returns>The ObjectId of the resource.</returns>
        public RmReference Create(RmResource newResource)
        {
            if (newResource == null)
                throw new ArgumentNullException("newResource");
            CreateRequest request = this.requestFactory.CreateCreateRequest(newResource);
            CreateResponse response = this.wsTransferFactoryClient.Create(request);
            try
            {
                RmReference reference = new RmReference(response.ResourceCreated.EndpointReference.ReferenceProperties.ResourceReferenceProperty.Value);
                newResource.ObjectID = reference;
                return reference;
            }
            catch (NullReferenceException)
            {
                return new RmReference();
            }
            catch (FormatException)
            {
                return new RmReference();
            }
        }

        /// <summary>
        /// Retrieves the object with the given ObjectId
        /// </summary>
        /// <param name="objectId">The ObjectId of the requested object.</param>
        /// <returns>The object or null if not found</returns>
        /// <exception cref="System.ServiceModel.FaultException">System.ServiceModel.FaultException thrown when failures occur.</exception>
        public RmResource Get(RmReference objectId)
        {
            return Get(objectId, null, null);
        }

        /// <summary>
        /// Retrieves the representation of an object with the given ObjectId in the given culture.
        /// </summary>
        /// <param name="objectId">The ObjectId of the requested object.</param>
        /// <param name="culture">The requested culture representation of the object.</param>
        /// <returns>The object or null if not found.</returns>
        public RmResource Get(RmReference objectId, CultureInfo culture)
        {
            return Get(objectId, culture, null);
        }

        /// <summary>
        /// Retrieves the object and the specified attributes with the given ObjectId.
        /// </summary>
        /// <param name="objectId">The ObjectId of the requested object.</param>
        /// <param name="attributes">The list of attributes on the object to return.</param>
        /// <returns></returns>
        public RmResource Get(RmReference objectId, String[] attributes)
        {
            return Get(objectId, null, attributes);
        }

        protected RmResource Get(RmReference objectId, CultureInfo culture, String[] attributes)
        {
            GetRequest request = this.requestFactory.CreateGetRequest(objectId, culture, attributes);
            GetResponse response = this.wsTransferClient.Get(request);
            return this.resourceFactory.CreateResource(response);
        }

        /// <summary>
        /// Saves changes made to an object recorded by the transaction to the service.
        /// </summary>
        /// <param name="transaction">The transaction object which recorded changes made to a Resource object.</param>
        /// <returns>True upon successful operation.</returns>
        public bool Put(RmResourceChanges transaction)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }

            PutRequest request = this.requestFactory.CreatePutRequest(transaction);
            PutResponse response = this.wsTransferClient.Put(request);
            if (response == null)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Deletes the object with the given ObjectId.
        /// </summary>
        /// <param name="objectId">The ObjectId of the object to delete.</param>
        /// <returns>True upon successful deletion.</returns>
        public bool Delete(RmReference objectId)
        {
            DeleteRequest request = this.requestFactory.CreateDeleteRequest(objectId);
            DeleteResponse response = this.wsTransferClient.Delete(request);
            if (response == null)
                return false;
            else
                return true;
        }
        #endregion

        #region Enumeration
        /// <summary>
        /// Returns an enumerator that can traverse all objects matching the given filter.
        /// </summary>
        /// <param name="filter">The XPath filter of which objects to select.</param>
        /// <returns>An enumerator object which can be consumed in foreach statements.</returns>
        public IEnumerable<RmResource> Enumerate(String filter)
        {
            return this.Enumerate(filter, new string[] {});
        }

        /// <summary>
        /// Returns an enumerator that can traverse all objects matching the given filter.
        /// 
        /// Each object only contains the specified attributes.
        /// </summary>
        /// <param name="filter">The XPath filter of which objects to select.</param>
        /// <param name="attributes">A list of attribute names to include in returned objects.</param>
        /// <returns>An enumerator object which can be consumed in foreach statements.</returns>
        public IEnumerable<RmResource> Enumerate(String filter, String[] attributes)
        {
            if (String.IsNullOrEmpty(filter))
            {
                throw new ArgumentNullException("filter");
            }

            return this.requestFactory.CreateEnumeration(this.wsEnumerationClient, this.resourceFactory, filter, attributes);
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.wsTransferClient.Close();
            this.wsTransferFactoryClient.Close();
            this.mexClient.Close();
            this.wsEnumerationClient.Close();

            GC.SuppressFinalize(this);
        }

        #endregion

        #region Promoted Properties
        /// <summary>
        /// Gets or Sets the credential with which the underlying clients use to communicate with the service.
        /// </summary>
        public System.Net.NetworkCredential ClientCredential
        {
            get
            {
                return this.wsTransferClient.ClientCredentials.Windows.ClientCredential;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                this.wsTransferClient.ClientCredentials.Windows.ClientCredential = value;
                this.wsTransferFactoryClient.ClientCredentials.Windows.ClientCredential = value;
                this.wsEnumerationClient.ClientCredentials.Windows.ClientCredential = value;
                this.mexClient.ClientCredentials.Windows.ClientCredential = value;
            }
        }

        /// <summary>
        /// Gets or Sets the RmResourceFactory object used to construct resources returned.
        /// </summary>
        public RmResourceFactory ResourceFactory
        {
            get
            {
                return this.resourceFactory;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                this.resourceFactory = value;
            }
        }

        /// <summary>
        /// Gets or Sets the RmRequestFactory object used to construct request messages.
        /// </summary>
        public RmRequestFactory RequestFactory
        {
            get
            {
                return this.requestFactory;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                this.requestFactory = value;
            }
        }

        /// <summary>
        /// Returns true if the schema has been cached for this client.
        /// </summary>
        public bool SchemaCached
        {
            get
            {
                return this.schemaCached;
            }
        }
        #endregion
    }
}
